﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SuperSocket;
using SuperSocket.Channel;
using SuperSocket.ProtoBase;
using SuperSocket.Server;

namespace MSocketCore.MSocket
{
    public class MAppSession : IAppSession, ILogger, ILoggerAccessor
    {
        private IChannel _channel;
        private Dictionary<object, object> _items;

        protected internal IChannel Channel
        {
            get
            {
                return this._channel;
            }
        }

        void IAppSession.Initialize(IServerInfo server, IChannel channel)
        {
            this.SessionID = !(channel is IChannelWithSessionIdentifier sessionIdentifier) ? Guid.NewGuid().ToString() : sessionIdentifier.SessionIdentifier;
            this.Server = server;
            this.StartTime = DateTimeOffset.Now;
            this._channel = channel;
            this.State = SessionState.Initialized;
        }

        public string SessionID { get; private set; }

        public DateTimeOffset StartTime { get; private set; }

        public SessionState State { get; private set; }

        public IServerInfo Server { get; private set; }

        IChannel IAppSession.Channel
        {
            get
            {
                return this._channel;
            }
        }

        public object DataContext { get; set; }

        public EndPoint RemoteEndPoint
        {
            get
            {
                return this._channel?.RemoteEndPoint;
            }
        }

        public EndPoint LocalEndPoint
        {
            get
            {
                return this._channel?.LocalEndPoint;
            }
        }

        public DateTimeOffset LastActiveTime
        {
            get
            {
                IChannel channel = this._channel;
                return channel == null ? DateTimeOffset.MinValue : channel.LastActiveTime;
            }
        }

        public event AsyncEventHandler Connected;

        public event AsyncEventHandler<CloseEventArgs> Closed;

        public object this[object name]
        {
            get
            {
                Dictionary<object, object> items = this._items;
                if (items == null)
                    return (object)null;
                object obj;
                return items.TryGetValue(name, out obj) ? obj : (object)null;
            }
            set
            {
                MAppSession appSession = this;
                bool lockTaken = false;
                try
                {
                    Monitor.Enter((object)appSession, ref lockTaken);
                    (this._items ?? (this._items = new Dictionary<object, object>()))[name] = value;
                }
                finally
                {
                    if (lockTaken)
                        Monitor.Exit((object)appSession);
                }
            }
        }

        protected virtual ValueTask OnSessionClosedAsync(CloseEventArgs e)
        {
            return new ValueTask();
        }

        internal async ValueTask FireSessionClosedAsync(CloseEventArgs e)
        {
            MAppSession appSession = this;
            appSession.State = SessionState.Closed;
            await appSession.OnSessionClosedAsync(e);
            AsyncEventHandler<CloseEventArgs> closed = appSession.Closed;
            if (closed == null)
                return;
            await closed((object)appSession, e);
        }

        protected virtual ValueTask OnSessionConnectedAsync()
        {
            return new ValueTask();
        }

        internal async ValueTask FireSessionConnectedAsync()
        {
            MAppSession appSession = this;
            appSession.State = SessionState.Connected;
            await appSession.OnSessionConnectedAsync();
            AsyncEventHandler connected = appSession.Connected;
            if (connected == null)
                return;
            await connected((object)appSession, EventArgs.Empty);
        }

        public ValueTask SendAsync(ReadOnlyMemory<byte> data)
        {
            return this._channel.SendAsync(data);
        }

        public ValueTask SendAsync<TPackage>(
          IPackageEncoder<TPackage> packageEncoder,
          TPackage package)
        {
            return this._channel.SendAsync<TPackage>(packageEncoder, package);
        }

        void IAppSession.Reset()
        {
            this.ClearEvent<AsyncEventHandler>(ref this.Connected);
            this.ClearEvent<AsyncEventHandler<CloseEventArgs>>(ref this.Closed);
            this._items?.Clear();
            this.State = SessionState.None;
            this._channel = (IChannel)null;
            this.DataContext = (object)null;
            this.StartTime = new DateTimeOffset();
            this.Server = (IServerInfo)null;
            this.Reset();
        }

        protected virtual void Reset()
        {
        }

        private void ClearEvent<TEventHandler>(ref TEventHandler sessionEvent) where TEventHandler : Delegate
        {
            if ((Delegate)sessionEvent == (Delegate)null)
                return;
            foreach (Delegate invocation in sessionEvent.GetInvocationList())
                sessionEvent = Delegate.Remove((Delegate)sessionEvent, invocation) as TEventHandler;
        }

        public virtual async ValueTask CloseAsync()
        {
            await this.CloseAsync(CloseReason.LocalClosing);
        }

        public virtual async ValueTask CloseAsync(CloseReason reason)
        {
            IChannel channel = this.Channel;
            if (channel == null)
                return;
            try
            {
                await channel.CloseAsync(reason);
            }
            catch
            {
            }
        }

        private ILogger GetLogger()
        {
            return (this.Server as ILoggerAccessor).Logger;
        }

        void ILogger.Log<TState>(
          LogLevel logLevel,
          EventId eventId,
          TState state,
          Exception exception,
          Func<TState, Exception, string> formatter)
        {
            this.GetLogger().Log<TState>(logLevel, eventId, state, exception, (Func<TState, Exception, string>)((s, e) => "Session[" + this.SessionID + "]: " + formatter(s, e)));
        }

        bool ILogger.IsEnabled(LogLevel logLevel)
        {
            return this.GetLogger().IsEnabled(logLevel);
        }

        IDisposable ILogger.BeginScope<TState>(TState state)
        {
            return this.GetLogger().BeginScope<TState>(state);
        }

        public ILogger Logger
        {
            get
            {
                return (ILogger)this;
            }
        }
    }
}
